/*
 * Copyright (c) 2012, marco.tamburelli@gmail.com
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.bmi.gwt.mi.rebind;

import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.bmi.gwt.mi.client.impl.ParamUtils;
import org.bmi.gwt.mi.rebind.ProxyFactoryCreator.ProxyCreatorHelper;
import org.bmi.gwt.mi.shared.MiObject;
import org.bmi.gwt.mi.shared.MiProxy;
import org.bmi.gwt.mi.shared.annotations.TargetMiObjectClass;
import org.bmi.gwt.mi.shared.communication.ptypes.Param;
import org.bmi.gwt.mi.shared.communication.var.ReturnVar;
import org.bmi.gwt.mi.shared.exceptions.InvalidArgumentException;
import org.bmi.gwt.mi.shared.exceptions.NotAvailableConstructorException;
import org.bmi.gwt.mi.shared.util.ProxyBase;
import org.bmi.gwt.mi.shared.util.ProxyBase.ProxyConstructor;

import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JConstructor;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.dev.generator.NameFactory;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

class ProxyCreator
{
	/** Simple name of class to be generated */
	private String generatedClassName = null;

	/** Package name of class to be generated */
	private String packageName = null;

	/** The helper containing required informations */
	private ProxyCreatorHelper proxyCreatorHelper;

	private String canonicalName;

	/**
	 * Standard constructor: creates a class implementing the provided interface
	 * type which should be sub-type of {@link MiProxy}.
	 * 
	 * @param logger
	 * @param context
	 * @param helper
	 * @throws UnableToCompleteException
	 */
	ProxyCreator(TreeLogger logger, GeneratorContext context, ProxyCreatorHelper helper)
			throws UnableToCompleteException
	{
		this.proxyCreatorHelper = helper;

		this.generatedClassName = helper.getProxyType().getSimpleSourceName() + "___Impl";
		this.packageName = helper.getProxyType().getPackage().getName();

		this.canonicalName = this.packageName + "." + this.generatedClassName;

		generateClass(logger, context);

		logger.log(TreeLogger.INFO, "Generated class " + this.canonicalName);
	}

	/**
	 * Returns the name of the generated class.
	 * 
	 * @return
	 */
	String getCanonicalName()
	{
		return this.canonicalName;
	}

	/**
	 * Returns true in case the target class is not an interface, and allows a
	 * constructor.
	 * 
	 * @return
	 */
	public boolean hasConstructor()
	{
		return this.proxyCreatorHelper.getTargteClass().isInterface() == null;
	}

	/**
	 * Returns the type of the provided interface.
	 * 
	 * @return
	 */
	public JClassType getInterfaceType()
	{
		return this.proxyCreatorHelper.getProxyType();
	}

	private void generateClass(TreeLogger logger, GeneratorContext context) throws UnableToCompleteException
	{
		TypeOracle typeOracle = context.getTypeOracle();
		PrintWriter printWriter = context.tryCreate(logger, this.packageName, this.generatedClassName);

		/*
		 * When the print writer is null, the source code has ALREADY been
		 * generated, in such case nothing will be done.
		 */
		if (printWriter == null) return;

		/* initialize composer, set class properties, create source writer */
		ClassSourceFileComposerFactory composer = null;
		composer = new ClassSourceFileComposerFactory(this.packageName, this.generatedClassName);

		composer.addImport(ProxyBase.class.getCanonicalName());
		composer.addImport(ProxyConstructor.class.getCanonicalName());
		composer.addImport(MiProxy.class.getCanonicalName());
		composer.addImport(List.class.getCanonicalName());
		composer.addImport(ArrayList.class.getCanonicalName());
		composer.addImport(Param.class.getCanonicalName());
		composer.addImport(ParamUtils.class.getCanonicalName());
		composer.addImport(NotAvailableConstructorException.class.getCanonicalName());
		composer.addImport(InvalidArgumentException.class.getCanonicalName());
		composer.addImport(ReturnVar.class.getCanonicalName());

		composer.setSuperclass(ProxyBase.class.getSimpleName());
		composer.addImplementedInterface(getInterfaceType().getQualifiedSourceName());

		SourceWriter sourceWriter = null;
		sourceWriter = composer.createSourceWriter(context, printWriter);

		/* Generating constructor and required methods */
		generateDefaultConstructor(logger, sourceWriter, typeOracle);
		generateArgumentConstructor(logger, sourceWriter, typeOracle);
		generateProxyMethods(logger, sourceWriter, typeOracle);

		/* close generated class */
		sourceWriter.outdent();
		sourceWriter.println("}");

		context.commit(logger, printWriter);
	}

	/**
	 * Generate source code for the default constructor.
	 * 
	 * @param logger
	 * @param w Source writer to output source code.
	 * @param typeOracle
	 * @throws UnableToCompleteException
	 */
	private void generateDefaultConstructor(TreeLogger logger, SourceWriter w, TypeOracle typeOracle)
			throws UnableToCompleteException
	{
		w.println("public %s() { ", this.generatedClassName);
		w.indent();

		w.println("super.targetMiObjectClass = %s.class;", this.proxyCreatorHelper.getTargteClass()
				.getQualifiedSourceName());
		w.println("super.constrSignature = \"\";");

		w.outdent();
		w.println("}");
	}

	protected void generateArgumentConstructor(TreeLogger logger, SourceWriter w, TypeOracle typeOracle)
			throws UnableToCompleteException
	{
		NameFactory nameFactory = new NameFactory();
		JConstructor[] constructors = this.proxyCreatorHelper.getTargteClass().getConstructors();
		String[] functionNames = new String[constructors.length];

		for (int i = 0; i < constructors.length; i++)
			functionNames[i] = generateCheckFunction(logger, w, typeOracle, constructors[i], nameFactory);

		w.println();

		w.println("public %s(Object[] args) throws %s { ", this.generatedClassName,
				NotAvailableConstructorException.class.getSimpleName());
		w.indent();

		for (int i = 0; i < functionNames.length; i++)
			w.println("if (%s(this, args)) return;", functionNames[i]);

		w.println("throw new %s();", NotAvailableConstructorException.class.getSimpleName());

		w.outdent();
		w.println("}");
	}

	private String createVarargs(SourceWriter w, JParameter[] params, JClassType stringType, JClassType dateType,
			JClassType isSerializable, JClassType miProxy, JClassType proxyBase, NameFactory nameFactory)
	{
		String argsList = nameFactory.createName("created");

		w.println("%s[] %s = new %s[%d];", Param.class.getSimpleName(), argsList, Param.class.getSimpleName(),
				params.length);
		w.println();

		for (int i = 0; i < params.length; ++i)
		{
			w.print("%s[%d] = ", argsList, i);

			JType pType = params[i].getType();

			JPrimitiveType pPrim = pType.isPrimitive();
			JClassType pClazz = pType.isClassOrInterface();
			JArrayType pArray = pType.isArray();

			if (pPrim != null)
			{
				switch (pPrim)
				{
				case BOOLEAN:
					w.println("%s.createBooleanParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
					break;
				case BYTE:
					w.println("%s.createByteParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
					break;
				case CHAR:
					w.println("%s.createCharacterParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
					break;
				case DOUBLE:
					w.println("%s.createDoubleParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
					break;
				case FLOAT:
					w.println("%s.createFloatParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
					break;
				case INT:
					w.println("%s.createIntegerParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
					break;
				case LONG:
					w.println("%s.createLongParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
					break;
				case SHORT:
					w.println("%s.createShortParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
					break;
				default:
					w.println("null;");
				}
			}
			else if (pClazz != null)
			{
				if (pClazz.isAssignableTo(dateType))
					w.println("%s.createDateParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
				else if (pClazz.isAssignableTo(stringType))
					w.println("%s.createStringParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
				else if (pClazz.isAssignableTo(isSerializable))
					w.println("%s.createObjectParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
				else if (pClazz.isAssignableTo(miProxy))
					w.println("%s.createProxyParam((%s)%s);", ParamUtils.class.getSimpleName(),
							proxyBase.getSimpleSourceName(), params[i].getName());
				else
					w.println("null;");
			}
			else if (pArray != null)
				w.println("%s.createArrayParam(%s);", ParamUtils.class.getSimpleName(), params[i].getName());
			else
				w.println("null;");
		}

		return argsList;
	}

	private String convertVarargs(SourceWriter w, JParameter[] params, JClassType stringType, JClassType dateType,
			JClassType isSerializable, JClassType proxyBase, String varargsName, NameFactory nameFactory)
	{
		String argsList = nameFactory.createName("converted");

		w.println("%s[] %s = new %s[%d];", Param.class.getSimpleName(), argsList, Param.class.getSimpleName(),
				params.length);
		w.println();

		for (int i = 0; i < params.length; ++i)
		{
			JType pType = params[i].getType();

			JPrimitiveType pPrim = pType.isPrimitive();
			JClassType pClazz = pType.isClassOrInterface();
			JArrayType pArray = pType.isArray();

			w.print("if (%s[%d] instanceof %s) ", varargsName, i, proxyBase.getSimpleSourceName());
			w.print("%s[%d] =  %s.createProxyParam((%s)%s[%d]);", argsList, i, ParamUtils.class.getSimpleName(),
					proxyBase.getSimpleSourceName(), varargsName, i);

			w.print("else if (%s[%d] instanceof %s) ", varargsName, i, ReturnVar.class.getSimpleName());
			w.println("%s[%d] =  %s.createObjectParam((%s)%s[%d]);", argsList, i, ParamUtils.class.getSimpleName(),
					ReturnVar.class.getSimpleName(), varargsName, i);

			w.print("else %s[%d] = ", argsList, i);

			if (pPrim != null)
			{
				switch (pPrim)
				{
				case BOOLEAN:
					w.println("%s.createBooleanParam((Boolean)%s[%d]);", ParamUtils.class.getSimpleName(), varargsName,
							i);
					break;
				case BYTE:
					w.print("%s.createByteParam((Byte)%s[%d]);", ParamUtils.class.getSimpleName(), varargsName, i);
					break;
				case CHAR:
					w.println("%s.createCharacterParam((Character)%s[%d]);", ParamUtils.class.getSimpleName(),
							varargsName, i);
					break;
				case DOUBLE:
					w.print("%s.createDoubleParam((Double)%s[%d]);", ParamUtils.class.getSimpleName(), varargsName, i);
					break;
				case FLOAT:
					w.println("%s.createFloatParam((Float)%s[%d]);", ParamUtils.class.getSimpleName(), varargsName, i);
					break;
				case INT:
					w.print("%s.createIntegerParam((Integer)%s[%d]);", ParamUtils.class.getSimpleName(), varargsName, i);
					break;
				case LONG:
					w.println("%s.createLongParam((Long)%s[%d]);", ParamUtils.class.getSimpleName(), varargsName, i);
					break;
				case SHORT:
					w.println("%s.createShortParam((Short)%s[%d]);", ParamUtils.class.getSimpleName(), varargsName, i);
					break;
				default:
					w.println("null;");
				}
			}
			else if (pClazz != null)
			{
				if (pClazz.isAssignableTo(dateType))
					w.println("%s.createDateParam((java.util.Date)%s[%d]);", ParamUtils.class.getSimpleName(),
							varargsName, i);
				else if (pClazz.isAssignableTo(stringType))
					w.println("%s.createStringParam((String)%s[%d]);", ParamUtils.class.getSimpleName(), varargsName, i);
				else if (pClazz.isAssignableTo(isSerializable))
					w.println("%s.createObjectParam((%s)%s[%d]);", ParamUtils.class.getSimpleName(),
							isSerializable.getQualifiedSourceName(), varargsName, i);
				else
					w.println("null;");
			}
			else if (pArray != null)
				w.println("%s.createArrayParam((%s)%s[%d]);", ParamUtils.class.getSimpleName(),
						pArray.getParameterizedQualifiedSourceName(), varargsName, i);
			else
				w.println("null;");
		}

		return argsList;
	}

	private String generateCheckFunction(TreeLogger logger, SourceWriter w, TypeOracle typeOracle,
			JConstructor constructor, NameFactory nameFactory) throws UnableToCompleteException
	{
		w.println();

		String functionName = nameFactory.createName("checkConstructor");

		w.println("private static boolean %s(final %s proxy, Object[] args) { ", functionName, this.generatedClassName);
		w.indent();

		JParameter[] parameters = constructor.getParameters();

		w.println("if (%d != args.length) return false;", parameters.length);
		w.println();

		StringBuilder signature = new StringBuilder();

		for (int i = 0; i < parameters.length; i++)
		{
			JType paramType = parameters[i].getType();
			String paramSignature = paramType.getJNISignature();

			w.println("if (!(args[%d] instanceof %s) ", i, paramType.getQualifiedSourceName());
			w.indent();
			w.println("&& !(args[%d] instanceof %s && ((%s)args[%d]).getJsni().equals(\"%s\")) ", i,
					ReturnVar.class.getCanonicalName(), ReturnVar.class.getCanonicalName(), i, paramSignature);

			JClassType miObjectArg = paramType.isClassOrInterface();

			if (miObjectArg != null)
			{
				List<JClassType> proxyArgs = this.proxyCreatorHelper.getProxiesToClass(miObjectArg);

				if (proxyArgs != null) for (JClassType p : proxyArgs)
					w.println("&& !(args[%d] instanceof %s) ", i, p.getQualifiedSourceName());
			}
			w.outdent();
			w.println(") return false;", NotAvailableConstructorException.class.getSimpleName());

			signature.append(paramSignature);
		}

		w.println("proxy.targetMiObjectClass = %s.class;", this.proxyCreatorHelper.getTargteClass()
				.getQualifiedSourceName());
		w.println("proxy.constrSignature = \"%s\";", signature.toString());
		w.println();

		/* Initializing the generator of the constructor */
		w.println();

		w.println("proxy.constrPublisher = new %s() {", ProxyConstructor.class.getSimpleName());
		w.indent();

		JClassType isSerializable;
		JClassType proxyBase;
		JClassType stringType;
		JClassType dateType;
		try
		{
			isSerializable = typeOracle.getType(IsSerializable.class.getCanonicalName());
			proxyBase = typeOracle.getType(ProxyBase.class.getCanonicalName());
			stringType = typeOracle.getType(String.class.getCanonicalName());
			dateType = typeOracle.getType(Date.class.getCanonicalName());
		}
		catch (NotFoundException e)
		{
			logger.log(TreeLogger.ERROR,
					"Can't find " + ReturnVar.class.getCanonicalName() + " or " + MiProxy.class.getCanonicalName(), e);
			throw new UnableToCompleteException();
		}

		String varargsName = nameFactory.createName("args");

		w.println("public void doConstructor(Object[] %s) throws %s {", varargsName,
				InvalidArgumentException.class.getSimpleName());
		w.indent();
		String args = convertVarargs(w, constructor.getParameters(), stringType, dateType, isSerializable, proxyBase,
				varargsName, nameFactory);

		w.println();

		w.println(
				"proxy.publisher.publishConstructor(proxy.objectId, proxy.targetMiObjectClass, proxy.constrSignature, %s);",
				args);

		w.outdent();
		w.println("}");

		w.outdent();
		w.println("};");

		w.println("return true;");

		w.outdent();
		w.println("}");

		return functionName;
	}

	protected void generateProxyMethods(TreeLogger logger, SourceWriter w, TypeOracle typeOracle)
			throws UnableToCompleteException
	{
		JMethod[] methods = getInterfaceType().getOverridableMethods();

		for (JMethod method : methods)
			if (method.isPublic()) generateProxyMethod(logger, w, typeOracle, method);
	}

	private void generateProxyMethod(TreeLogger logger, SourceWriter w, TypeOracle typeOracle, JMethod method)
			throws UnableToCompleteException
	{
		NameFactory nameFactory = new NameFactory();

		w.println();

		/* Write the method signature */
		JType returnType = method.getReturnType();

		w.print("public ");
		w.print(returnType.getQualifiedSourceName());
		w.print(" ");
		w.print(method.getName() + "(");

		JParameter[] params = method.getParameters();
		StringBuilder signature = new StringBuilder();
		JClassType retVarClass;
		JClassType miProxyType;
		JClassType miObjectType;

		JClassType serializable;
		JClassType isSerializable;

		JClassType miProxy;
		JClassType proxyBase;

		JClassType stringType;
		JClassType dateType;
		try
		{
			retVarClass = typeOracle.getType(ReturnVar.class.getCanonicalName());
			miProxyType = typeOracle.getType(MiProxy.class.getCanonicalName());
			miObjectType = typeOracle.getType(MiObject.class.getCanonicalName());

			serializable = typeOracle.getType(Serializable.class.getCanonicalName());
			isSerializable = typeOracle.getType(IsSerializable.class.getCanonicalName());

			miProxy = typeOracle.getType(MiProxy.class.getCanonicalName());
			proxyBase = typeOracle.getType(ProxyBase.class.getCanonicalName());

			stringType = typeOracle.getType(String.class.getCanonicalName());
			dateType = typeOracle.getType(Date.class.getCanonicalName());
		}
		catch (NotFoundException e)
		{
			logger.log(TreeLogger.ERROR,
					"Can't find " + ReturnVar.class.getCanonicalName() + " or " + MiProxy.class.getCanonicalName(), e);
			throw new UnableToCompleteException();
		}

		for (int i = 0; i < params.length; ++i)
		{
			if (i > 0) w.print(", ");

			JType paramType = params[i].getType();

			signature.append(getExpectedParameterType(logger, paramType, retVarClass, miProxyType, miObjectType,
					serializable, isSerializable, typeOracle, method).getJNISignature());

			paramType = paramType.getErasedType();

			w.print(paramType.getQualifiedSourceName());
			w.print(" ");

			/* We want to remember the name of parameter to avoid a duplication. */
			String paramName = params[i].getName();
			nameFactory.addName(paramName);

			w.print(paramName);
		}

		w.println(")");

		JClassType[] _throws = method.getThrows();

		for (int i = 0; i < _throws.length; i++)
		{
			if (i == 0)
				w.print("throws ");
			else
				w.print(", ");

			String exceptionName = _throws[i].getQualifiedSourceName();

			w.print("%s", exceptionName);
		}

		w.print(" {");

		w.indent();

		w.println("try {");
		w.indent();

		/* Creating the list */
		String argsList = createVarargs(w, params, stringType, dateType, isSerializable, miProxy, proxyBase,
				nameFactory);

		boolean returnsVoid = (returnType.isPrimitive() == JPrimitiveType.VOID);

		if (!returnsVoid)
		{
			try
			{
				JClassType returnClass = returnType.isClassOrInterface();
				JClassType proxyInt = typeOracle.getType(MiProxy.class.getCanonicalName());

				if (returnClass == null || !proxyInt.isAssignableFrom(returnClass))
				{
					logger.log(
							TreeLogger.ERROR,
							"Return type of " + method.getName() + " of class "
									+ getInterfaceType().getQualifiedSourceName() + " must implement "
									+ MiProxy.class.getCanonicalName());
					throw new UnableToCompleteException();
				}
			}
			catch (NotFoundException e)
			{
				logger.log(TreeLogger.ERROR, "Can't find " + MiProxy.class.getCanonicalName(), e);
				throw new UnableToCompleteException();
			}

			w.print("return (%s)", returnType.getQualifiedSourceName());
		}

		w.print("super.publisher.publishMethod(super.objectId, super.targetMiObjectClass, \"%s\", \"%s\", %s",
				method.getName(), signature, argsList);

		/*
		 * In case ther's a not void return type, the proper method from
		 * publisher class will be invoked.
		 */
		if (returnsVoid)
			w.println(");");
		else
		{
			JType targetReturnType = getExpectedParameterType(logger, returnType, retVarClass, miProxyType,
					miObjectType, serializable, isSerializable, typeOracle, method);
			w.println(", \"%s\", %s.class);", targetReturnType.getJNISignature(), returnType.getQualifiedSourceName());
		}

		w.outdent();
		w.println("}");

		w.println("catch(%s %s){", InvalidArgumentException.class.getSimpleName(), nameFactory.createName("ex"));
		w.indent();

		if (!returnsVoid) w.println("return null;");

		w.outdent();
		w.println("}");

		w.outdent();
		w.println("}");

		logger.log(TreeLogger.INFO, "Generated invoker for method " + method.getName() + signature);
	}

	private JType getExpectedParameterType(TreeLogger logger, JType paramType, JClassType retVarClass,
			JClassType miProxyType, JClassType miObjectType, JClassType serializable, JClassType isSerializable,
			TypeOracle typeOracle, JMethod method) throws UnableToCompleteException
	{
		JParameterizedType parametrizedType = paramType.isParameterized();
		JClassType paramProxyType = paramType.isInterface();

		JType expectedType;

		if (parametrizedType != null && retVarClass.isAssignableFrom(parametrizedType))
			/*
			 * If it's parameterized and is a subclass of ReturnVar the expected
			 * parameters for the real execution will be obtained from the type
			 * arguments.
			 */
			expectedType = parametrizedType.getTypeArgs()[0];
		else if (paramProxyType != null && miProxyType.isAssignableFrom(paramProxyType))
		{
			/*
			 * If the argument is a proxy, for the signature must be used the
			 * target object declared in the annotation of the proxy.
			 */
			TargetMiObjectClass anno = paramProxyType.getAnnotation(TargetMiObjectClass.class);

			if (anno != null)
				try
				{
					expectedType = typeOracle.getType(anno.miObjectClass().getCanonicalName());
				}
				catch (NotFoundException e)
				{
					logger.log(TreeLogger.ERROR, "Can't find " + anno.miObjectClass().getCanonicalName(), e);
					throw new UnableToCompleteException();
				}
			else
			{
				logger.log(TreeLogger.ERROR, "Impossible to generated the signature of method " + method.getName()
						+ " because type " + paramProxyType.getQualifiedSourceName() + " has no target object defined.");
				throw new UnableToCompleteException();
			}
		}
		else
			expectedType = paramType;

		if (expectedType.isPrimitive() != null) return expectedType;

		JType leafType = expectedType.getLeafType();
		JClassType expectedClassType = leafType.isClassOrInterface();

		if (leafType.isPrimitive() != null || serializable.isAssignableFrom(expectedClassType)
				|| isSerializable.isAssignableFrom(expectedClassType)
				|| miObjectType.isAssignableFrom(expectedClassType)) return expectedType;

		logger.log(TreeLogger.ERROR, "Impossible to generated the method " + method.getName() + " because type "
				+ expectedType.getQualifiedSourceName() + " can't be serialized.");

		throw new UnableToCompleteException();
	}
}
